feat: enhance file download and caching mechanisms#404
Conversation
|
| const ALLOWED_BLOCK_SIZE_MB = new Set([1, 2, 4, 8]); | ||
|
|
||
| function getConfiguredBlockSizeInMb() { | ||
| const configuredValue = process.env.INTERNXT_DRIVE_DOWNLOAD_BLOCK_SIZE_MB; |
There was a problem hiding this comment.
Why this needs to be a enviroment variable
There was a problem hiding this comment.
My idea is that these values should be easy to change. Right now, the optimal settings are 4 MB per download and 3 prefetch blocks. These values can be increased if changes are made to the backend to reduce the latency of download requests, which would greatly improve performance.
Looking back, we can adjust these values to achieve better performance if the conditions are right; that’s why I set them as environment variables so they can be easily modified.
| blockLength, | ||
| }: Props): Promise<Result<void, Error>> { | ||
| if (isAborted(state)) return { data: undefined }; | ||
| if (blockLength <= 0 || blockStart >= virtualFile.size) return { data: undefined }; |
There was a problem hiding this comment.
Is this something that can happen? meaning: Fuse can ask for a block that is negative?
There was a problem hiding this comment.
It's not just to validate negative ranges; it's also to prevent readings in the 0 range—which fuse does sometimes—and to ensure that invalid ranges aren't attempted, which is rare but does happen.
| return { blockStart: Math.max(0, Math.min(range.position, fileSize)), blockLength: 0 }; | ||
| } | ||
|
|
||
| const clampedStart = Math.max(0, range.position); |
There was a problem hiding this comment.
Why? i assume that range will always be 0 or more
There was a problem hiding this comment.
We might assume that's the case, but there are very rare instances where it might not be; this change is a simple safeguard that definitively prevents that from happening.
| import { type HandleReadDeps, type ReadRange } from './types'; | ||
| import { isThumbnailProcess } from './thumbnail-processes'; | ||
|
|
||
| const PREFETCH_DEFAULT_BLOCKS_AHEAD = 5; |
| function getThumbnailPrefetchBlocksAhead() { | ||
| return getPrefetchBlocksAhead({ | ||
| configuredValue: process.env.INTERNXT_DRIVE_THUMBNAIL_PREFETCH_BLOCKS_AHEAD, | ||
| }); | ||
| } | ||
|
|
||
| function getReadPrefetchBlocksAhead() { | ||
| return getPrefetchBlocksAhead({ | ||
| configuredValue: process.env.INTERNXT_DRIVE_READ_PREFETCH_BLOCKS_AHEAD, | ||
| }); | ||
| } |
There was a problem hiding this comment.
As I mentioned earlier, the idea is to be able to easily change the values.
| return Math.max(0, Math.min(parsed, PREFETCH_MAX_BLOCKS_AHEAD)); | ||
| } | ||
|
|
||
| function getThumbnailPrefetchBlocksAhead() { |
There was a problem hiding this comment.
Why, for a thumbnail, we need to do a prefetch? would not this slow the initial load of the file explorer?
There was a problem hiding this comment.
I've been running tests focused on thumbnails, and you're right—the improvement is very small, and it slows down the network for other processes.
| } | ||
| } | ||
|
|
||
| function schedulePrefetchBlocksAhead({ |
There was a problem hiding this comment.
Shouldnt we test this separtedly since it has a lot of logic?
| }): Promise<Result<void, Error>> { | ||
| const { blockStart, blockLength } = expandToBlockBoundaries({ range, fileSize: virtualFile.size }); | ||
|
|
||
| if (blockLength <= 0 || blockStart >= virtualFile.size) { |
There was a problem hiding this comment.
Again, if this is a realistic scenario, why not prevent from calling this whole functionality?
There was a problem hiding this comment.
This security check has been deliberately included as a protective measure.
Even though we attempt to filter out such cases beforehand, this path can still be reached in realistic extreme scenarios (reads at the end of the file, zero-length reads, obsolete ranges following changes in file size).
| const downloads = missingBlocks.map((block) => { | ||
| const start = block * BLOCK_SIZE; | ||
| const end = Math.min(start + BLOCK_SIZE, virtualFile.size); | ||
| const boundedBlockLength = end - start; |
There was a problem hiding this comment.
Same, i feel like there has to be a way to prevent this whole "negative block request" instead of just adding everywhere the check, this just adds unnecessary complexity dont you think
There was a problem hiding this comment.
You're right—the extra validation was too conservative; the first validation at the beginning of the function is enough.
| if ( | ||
| !shouldEmitProgress({ | ||
| now: Date.now(), | ||
| bytesDownloaded, | ||
| fileSize, | ||
| state: progressReporterState, | ||
| }) | ||
| ) { | ||
| return; | ||
| } | ||
|
|
There was a problem hiding this comment.
This is just to avoid spamming progress updates.
Downloads can report progress very frequently, and without this check we would send too many UI updates for very small changes.
So this acts like a small throttle: it only emits from time to time, but still allows the final update through when the download reaches 100%.
| const PROGRESS_UPDATE_INTERVAL_MS = 250; | ||
|
|
||
| type ProgressReporterState = { | ||
| lastUpdateAt: number; | ||
| }; | ||
|
|
||
| function shouldEmitProgress({ | ||
| now, | ||
| bytesDownloaded, | ||
| fileSize, | ||
| state, | ||
| }: { | ||
| now: number; | ||
| bytesDownloaded: number; | ||
| fileSize: number; | ||
| state: ProgressReporterState; | ||
| }) { | ||
| const reachedEnd = bytesDownloaded >= fileSize; | ||
| const elapsedSinceLastUpdate = now - state.lastUpdateAt; | ||
|
|
||
| if (!reachedEnd && elapsedSinceLastUpdate < PROGRESS_UPDATE_INTERVAL_MS) { | ||
| return false; | ||
| } | ||
|
|
||
| state.lastUpdateAt = now; | ||
| return true; | ||
| } | ||
|
|
There was a problem hiding this comment.
IF this is necessary this should be in its own file, where we should test it separatedly
| let bytesRead = 0; | ||
| let buffer = Buffer.alloc(length); | ||
|
|
||
| response.data.on('data', (chunk: Uint8Array) => { | ||
| const source = Buffer.from(chunk); | ||
| const requiredLength = bytesRead + source.length; | ||
|
|
||
| if (requiredLength > buffer.length) { | ||
| const next = Buffer.alloc(Math.max(buffer.length * 2, requiredLength)); | ||
| buffer.copy(next, 0, 0, bytesRead); | ||
| buffer = next; | ||
| } | ||
|
|
||
| source.copy(buffer, bytesRead); | ||
| bytesRead += source.length; | ||
| }); |
There was a problem hiding this comment.
We need to do this because the HTTP response comes back as a stream, not as a complete Buffer.
So before returning, we have to collect all the chunks and build the final contiguous buffer.
If we returned earlier, we could end up decrypting or processing incomplete data.
|
Warning Review limit reached
Next review available in: 57 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (27)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |


What is Changed / Added
Why